/*
 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Disposable, GlobalStateManager, MutableState, StateManager } from "@koalaui/runtime"

/**
 * Defines the subscribed abstract property.
 * @since 7
 * @systemapi
 */
export abstract class SubscribedAbstractProperty<T> {
    /**
     * Returns the property name,
     * e.g. let link = AppStorage.Link("foo") then link.info() == "foo"
     *
     * @returns { string } the property name if set or undefined
     * @syscap SystemCapability.ArkUI.ArkUI.Full
     * @crossplatform
     * @since 10
     */
    info(): string {
        return "TODO"
    }
    /**
     * Called when data is obtained.
     * @since 7
     * @systemapi
     */
    abstract get(): T

    /**
     * Called when data is created.
     * @since 7
     * @systemapi
     */
    abstract set(newValue: T): void
    /**
     * An app needs to call this function before the instance of SubscribedAbstractProperty
     * goes out of scope / is subject to garbage collection. Its purpose is to unregister the
     * variable from the two-way/one-way sync relationship that AppStorage/LocalStorage.link()/prop()
     * and related functions create.
     *
     * @syscap SystemCapability.ArkUI.ArkUI.Full
     * @crossplatform
     * @since 10
     */
    abstract aboutToBeDeleted(): void
}

interface Mutable {
    readonly mutable: boolean
}

class StorageEntry<V> extends SubscribedAbstractProperty<V> implements Disposable, Mutable {
    readonly state: MutableState<V>
    readonly mutable: boolean

    constructor(state: MutableState<V>, mutable?: boolean) {
        super()
        this.state = state
        this.mutable = mutable ?? true
    }

    get disposed(): boolean {
        return this.state.disposed
    }

    dispose(): void {
        this.state.dispose()
    }

    aboutToBeDeleted(): void {
        // todo
    }

    get(): V {
        return this.state.value
    }

    set(value: V): void {
        if (this.mutable) this.state.value = value
    }
}

export class StorageMap {
    private static _shared?: StorageMap
    private readonly manager: StateManager = GlobalStateManager.instance
    private readonly map: Map<string, Object> = new Map<string, Object>()

    static get shared(): StorageMap {
        if (StorageMap._shared?.manager != GlobalStateManager.instance) {
            StorageMap._shared = new StorageMap() // recreate shared storage if state manager is changed
        }
        return StorageMap._shared as StorageMap
    }

    constructor(initializer?: IterableIterator<[string, Object]>) {
        if (initializer) {
            for (const entry of initializer) {
                this.create<Object>(entry[0], entry[1])
            }
        }
    }

    keys(): IterableIterator<string> {
        return this.map.keys()
    }

    get size(): number {
        return this.map.size
    }

    has(key: string): boolean {
        return this.map.has(key)
    }

    entry<V>(key: string): StorageEntry<V> | undefined {
        return this.map.get(key) as StorageEntry<V> | undefined
    }

    entryObject(key: string): Object | undefined {
        return this.map.get(key)
    }

    get<V>(key: string): V | undefined {
        const entry = this.entry<V>(key)
        return entry?.get()
    }

    set<V>(key: string, value: V, create?: boolean, mutable?: boolean): boolean {
        const entry = this.entry<V>(key)
        if (entry) {
            entry.set(value)
            return true
        }
        if (create ?? true) {
            this.create(key, value, mutable ?? true)
        }
        return false
    }

    link<V>(key: string, value: V): StorageEntry<V> {
        const entry = this.entry<V>(key)
        return entry ? entry : this.create(key, value)
    }

    delete(key: string): boolean {
        const entry = this.entryObject(key)
        if (!entry)
            return false;
        (entry as Disposable).dispose();
        return this.map.delete(key)
    }

    clear(): boolean {
        if (this.map.size == 0) return false
        for (const entry of this.map.values()) {
            (entry as Disposable).dispose()
        }
        this.map.clear()
        return true
    }

    create<V>(key: string, value: V, mutable?: boolean): StorageEntry<V> {
        const entry = new StorageEntry<V>(this.manager.mutableState<V>(value, true), mutable ?? true)
        this.map.set(key, entry)
        return entry
    }
}

export class LocalStorage {
    private static shared?: LocalStorage
    /** @internal */
    readonly map: StorageMap

    /**
     * Constructor.
     * @since 9
     */
    constructor(initializingProperties?: IterableIterator<[string, Object]>) {
        this.map = new StorageMap(initializingProperties)
    }

    /**
     * Get current LocalStorage shared from stage.
     * @StageModelOnly
     * @since 9
     * @deprecated since 10
     */
    static GetShared(): LocalStorage {
        return LocalStorage.getShared()
    }
    /**
     * Get current LocalStorage shared from stage.
     * @StageModelOnly
     * @since 10
     */
    static getShared(): LocalStorage {
        if (!LocalStorage.shared) {
            LocalStorage.shared = new LocalStorage()
        }
        return LocalStorage.shared as LocalStorage
    }

    /**
     * Return true if property with given name exists
     * @since 9
     */
    has(propName: string): boolean {
        return this.map.has(propName)
    }

    /**
     * Return a Map Iterator
     * @since 9
     */
    keys(): IterableIterator<string> {
        return this.map.keys()
    }

    /**
     * Return number of properties
     * @since 9
     */
    size(): number {
        return this.map.size
    }

    /**
     * Return value of given property
     * @since 9
     */
    get<V>(propName: string): V | undefined {
        return this.map.get<V>(propName)
    }

    /**
     * Set value of given property
     * @since 9
     */
    set<V>(propName: string, newValue: V): boolean {
        return this.map.set<V>(propName, newValue, false)
    }

    /**
     * Add property if not property with given name
     * @since 9
     */
    setOrCreate<V>(propName: string, newValue: V): boolean {
        this.map.set<V>(propName, newValue, true)
        return newValue != undefined
    }

    /**
     * Create and return a 'link' (two-way sync) to named property
     * @since 9
     */
    link<V>(propName: string): SubscribedAbstractProperty<V> {
        throw new Error("LocalStorage.link is not implemented")
    }

    /**
     * Like link(), will create and initialize a new source property in LocalStorge if missing
     * @since 9
     */
    setAndLink<V>(propName: string, defaultValue: V): SubscribedAbstractProperty<V> {
        throw new Error("LocalStorage.setAndLink is not implemented")
    }

    /**
     * Create and return a 'prop' (one-way sync) to named property
     * @since 9
     */
    prop<S>(propName: string): SubscribedAbstractProperty<S> {
        throw new Error("LocalStorage.prop is not implemented")
    }

    /**
     * Like prop(), will create and initialize a new source property in LocalStorage if missing
     * @since 9
     */
    setAndProp<S>(propName: string, defaultValue: S): SubscribedAbstractProperty<S> {
        throw new Error("LocalStorage.setAndProp is not implemented")
    }

    /**
     * Delete property from StorageBase
     * @since 9
     * @returns false if method failed
     */
    delete(propName: string): boolean {
        return this.map.delete(propName)
    }

    /**
     * Delete all properties from the StorageBase
     * @since 9
     */
    clear(): boolean {
        return this.map.clear()
    }
}

/**
 * Defines the AppStorage interface.
 * @since 7
 */
export class AppStorage {
    /**
     * Called when a link is set.
     * @since 7
     * @deprecated since 10
     */
    static Link<V>(propName: string): SubscribedAbstractProperty<V> {
        return AppStorage.link<V>(propName)
    }
    /**
     * Called when a link is set.
     * @since 10
     */
    static link<V>(propName: string): SubscribedAbstractProperty<V> {
        throw new Error("AppStorage.link is not implemented")
    }

    /**
     * Called when a hyperlink is set.
     * @since 7
     * @deprecated since 10
     */
    static SetAndLink<V>(propName: string, defaultValue: V): SubscribedAbstractProperty<V> {
        return AppStorage.setAndLink<V>(propName, defaultValue)
    }
    /**
     * Called when a hyperlink is set.
     * @since 10
     */
    static setAndLink<V>(propName: string, defaultValue: V): SubscribedAbstractProperty<V> {
        throw new Error("AppStorage.setAndLink is not implemented")
    }

    /**
     * Called when a property is set.
     * @since 7
     * @deprecated since 10
     */
    static Prop<V>(propName: string): SubscribedAbstractProperty<V> {
        return AppStorage.prop<V>(propName)
    }
    /**
     * Called when a property is set.
     * @since 10
     */
    static prop<V>(propName: string): SubscribedAbstractProperty<V> {
        throw new Error("AppStorage.prop is not implemented")
    }

    /**
     * Called when dynamic properties are set.
     * @since 7
     * @deprecated since 10
     */
    static SetAndProp<V>(propName: string, defaultValue: V): SubscribedAbstractProperty<V> {
        return AppStorage.setAndProp<V>(propName, defaultValue)
    }
    /**
     * Called when dynamic properties are set.
     * @since 10
     */
    static setAndProp<V>(propName: string, defaultValue: V): SubscribedAbstractProperty<V> {
        throw new Error("AppStorage.setAndProp is not implemented")
    }

    /**
     * Called when owning or not.
     * @since 7
     * @deprecated since 10
     */
    static Has(propName: string): boolean {
        return AppStorage.has(propName)
    }
    /**
     * Called when owning or not.
     * @since 10
     */
    static has(propName: string): boolean {
        return StorageMap.shared.has(propName)
    }

    /**
     * Called when data is obtained.
     * @since 7
     * @deprecated since 10
     */
    static Get<V>(propName: string): V | undefined {
        return AppStorage.get<V>(propName)
    }
    /**
     * Called when data is obtained.
     * @since 10
     */
    static get<V>(propName: string): V | undefined {
        return StorageMap.shared.get<V>(propName)
    }

    /**
     * Called when setting.
     * @since 7
     * @deprecated since 10
     */
    static Set<V>(propName: string, newValue: V): boolean {
        return AppStorage.set<V>(propName, newValue)
    }
    /**
     * Called when setting.
     * @since 10
     */
    static set<V>(propName: string, newValue: V): boolean {
        return StorageMap.shared.set<V>(propName, newValue, false)
    }

    /**
     * Called when setting or creating.
     * @since 7
     * @deprecated since 10
     */
    static SetOrCreate<V>(propName: string, newValue: V): void {
        AppStorage.setOrCreate<V>(propName, newValue)
    }
    /**
     * Called when setting or creating.
     * @since 10
     */
    static setOrCreate<V>(propName: string, newValue: V): void {
        StorageMap.shared.set<V>(propName, newValue, true)
    }

    /**
     * Called when a deletion is made.
     * @since 7
     * @deprecated since 10
     */
    static Delete(propName: string): boolean {
        return AppStorage.delete(propName)
    }
    /**
     * Called when a deletion is made.
     * @since 10
     */
    static delete(propName: string): boolean {
        return StorageMap.shared.delete(propName)
    }

    /**
     * Called when a dictionary is sorted.
     * @since 7
     * @deprecated since 10
     */
    static Keys(): IterableIterator<string> {
        return AppStorage.keys()
    }
    /**
     * Called when a dictionary is sorted.
     * @since 10
     */
    static keys(): IterableIterator<string> {
        return StorageMap.shared.keys()
    }

    /**
     * Called when a cleanup occurs.
     * @since 7
     * @deprecated since 9
     * @useinstead AppStorage.Clear
     */
    static staticClear(): boolean {
        return StorageMap.shared.clear()
    }

    /**
     * Called when a cleanup occurs.
     * @since 9
     * @deprecated since 10
     */
    static Clear(): boolean {
        return AppStorage.clear()
    }
    /**
     * Called when a cleanup occurs.
     * @since 10
     */
    static clear(): boolean {
        return StorageMap.shared.clear()
    }

    /**
     * Called when the data can be changed.
     * @since 7
     */
    static IsMutable(propName: string): boolean {
        const entry = StorageMap.shared.entryObject(propName)
        return entry ? (entry as Mutable).mutable : false
    }

    /**
     * Called when you check how much data is stored.
     * @since 7
     */
    static Size(): number {
        return AppStorage.size()
    }
    /**
     * Called when you check how much data is stored.
     * @since 10
     */
    static size(): number {
        return StorageMap.shared.size
    }
}
